﻿/*
 *Copyright (c) 2014, Riccardo Cagnasso
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

using System;
using System.Linq.Expressions;
using System.Text;
using System.Text.RegularExpressions;
using PasslibNet.Utils;

namespace PasslibNet.Algorithms
{
    /// <summary>
    /// IPasslibAlgorithm is an interface that every Algorithm has to
    /// implement in order to be used in Passlib.
    /// </summary>
    public interface IPasslibAlgorithm
    {
        /// <summary>
        /// Encrypt the password using the algorithm
        /// </summary>
        /// 
        /// <param name="secret">The password to be hashed</param>
        /// 
        /// <returns>An hash in Modular Crypt Format</returns>
        string Encrypt(string secret);

        /// <summary>
        /// Veryfies a password against a given MCF hash
        /// </summary>
        /// <param name="secret">The password to be verified</param>
        /// <param name="hash">A MCF hash that will be used to verify the password</param>
        /// <returns>true if the password is correct, false otherwise</returns>
        bool Verify(string secret, string hash);

        /// <summary>
        /// Check if a given hash was made using this algorithm
        /// </summary>
        /// <param name="hash">The hash to be checked</param>
        /// <returns>True if the hash was made using this algorithm, false otherwise</returns>
        bool Identify(string hash);

        /// <summary>
        /// Parse the given hash initializing the object with tha parameters from the hash
        /// The object can then be used to verify a password against this hash
        /// </summary>
        /// <param name="hash">The MCF hash to be parsed</param>
        void ParseHash(string hash);

        /// <summary>
        /// The (supposedly) MCF code used for this algorithm
        /// https://pythonhosted.org/passlib/modular_crypt_format.html
        /// </summary>
        string MFCCode { get; }
    }


    /// <summary>
    /// A generic class for hashing algorithms that have a Salt and
    /// a number of rounds as parameters
    /// </summary>
    public abstract class SaltRounds : IPasslibAlgorithm
    {
        /// <summary>
        /// The salt, as a base64 string
        /// </summary>
        public string Salt;

        /// <summary>
        /// The number of hashing rounds
        /// </summary>
        public int Rounds;
        public string MFCCode { get; protected set; }


        /// <summary>
        /// Constructor that can take a salt or the number of rounds
        /// </summary>
        /// <param name="salt">
        /// The salt, if a valid base64 string, will be used as-is, otherwise will be decoded as Unicode string and ancoded in base64
        /// Default is a salt generated with a cryptographic-safe random generator
        /// </param>
        /// <param name="rounds">
        /// The number of hashing rounds. This is useful to increase the time needed to compute a valid hash and therefore the robustness
        /// of the hash against brute force attacks. Default is 80000.
        /// </param>
        public SaltRounds(string salt = null, int rounds = 80000)
        {
            this.MFCCode = ModularCryptFormat.AlgoToCode[this.GetType()];

            try
            {
                // ReSharper disable once ReturnValueOfPureMethodIsNotUsed
                // ReSharper disable once AssignNullToNotNullAttribute
                // This is here for validate the string as base64
                Convert.FromBase64String(salt);
                this.Salt = salt;
            }
            catch (FormatException)
            {
                // ReSharper disable once AssignNullToNotNullAttribute
                // this was already checked up there
                this.Salt = Convert.ToBase64String(Encoding.Unicode.GetBytes(salt));
            }
            catch (ArgumentNullException)
            {
                this.Salt = RandomGenerator.GenerateBase64String();
            }

            this.Rounds = rounds;
        }

        /// <returns>
        /// An hash in Modular Crypt Format
        /// The format for this hash is $MCFcode$rounds=numRounds$Salt$Passowrd
        /// </returns>
        public string Encrypt(string secret)
        {
            return this.GenConfig() + '$' + this.Genhash(secret);
        }

        protected abstract string Genhash(string secret);

        public bool Verify(string secret, string hash)
        {
            var key = this.GetKeyFromHash(hash);

            return key == this.Genhash(secret);
        }

        public bool Identify(string hash)
        {
            try
            {
                return ModularCryptFormat.SplittedHash(hash)[0] == this.MFCCode;
            }
            catch (Exception)
            {
                return false;
            }
        }

        protected virtual string GenConfig()
        {
            return '$' + String.Join("$", new string[]
            {
                this.MFCCode,
                "rounds=" + this.Rounds,
                this.Salt
            });
        }

        public virtual void ParseHash(string hash)
        {
            var words = ModularCryptFormat.SplittedHash(hash);
            var matchRounds = Regex.Matches(words[1], @"^rounds=(\d*)", RegexOptions.IgnoreCase);
            this.Rounds = Int32.Parse(matchRounds[0].Groups[1].Value);
            this.Salt = words[2];
        }

        protected virtual string GetKeyFromHash(string hash)
        {
            return ModularCryptFormat.SplittedHash(hash)[3];
        }
    }
}
